package com.binlog.module;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.github.shyiko.mysql.binlog.event.EventType;
import com.google.common.collect.Maps;
import lombok.Data;
import lombok.SneakyThrows;

import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;

import static com.github.shyiko.mysql.binlog.event.EventType.isDelete;
import static com.github.shyiko.mysql.binlog.event.EventType.isWrite;

/**
 * Bin log item
 *
 * @description: 监听到的binlog item
 * @author: chenyj
 * @date 2023 -10-13 15:54:00
 */
@Data
public class BinLogItem implements Serializable {
    private static final long serialVersionUID = 5503152746318421290L;

    private String schema;
    private String table;
    private EventType eventType;
    private Long timestamp;
    private Long serverId;
    private Long positionId;
    // 存储字段-之前的值之后的值
    private Map<String, Serializable> before;
    private Map<String, Serializable> after;
    // 存储字段--类型
    private Map<String, TableColum> colums;

    /**
     * 新增或者删除操作数据格式化
     */
    public static BinLogItem itemFromInsertOrDeleted(Serializable[] row, Map<String, TableColum> columMap,
                                                     EventType eventType, long positionId) {
        if (row == null || columMap == null || row.length != columMap.size()) {
            return null;
        }

        BinLogItem item = new BinLogItem();
        item.eventType = eventType;
        item.colums = columMap;
        item.before = Maps.newHashMap();
        item.after = Maps.newHashMap();
        item.positionId = positionId;
        item.table = columMap.values().stream().findFirst().get().getTable();
        item.schema = columMap.values().stream().findFirst().get().getSchema();
        Map<String, Serializable> beOrAf = Maps.newHashMap();

        columMap.entrySet().forEach(entry -> {
            String key = entry.getKey();
            TableColum tableColum = entry.getValue();
            beOrAf.put(key, row[tableColum.getInx()]);
        });

        if (isWrite(eventType)) {
            item.after = beOrAf;
        }

        if (isDelete(eventType)) {
            item.before = beOrAf;
        }

        return item;
    }


    /**
     * 更新操作数据格式化
     */
    public static BinLogItem itemFromUpdate(Map.Entry<Serializable[], Serializable[]> mapEntry,
                                            Map<String, TableColum> columMap, EventType eventType, long positionId) {
        if (mapEntry == null || columMap == null) {
            return null;
        }

        BinLogItem item = new BinLogItem();
        item.eventType = eventType;
        item.colums = columMap;
        item.before = Maps.newHashMap();
        item.after = Maps.newHashMap();
        item.positionId = positionId;
        item.table = columMap.values().stream().findFirst().get().getTable();
        item.schema = columMap.values().stream().findFirst().get().getSchema();
        Map<String, Serializable> be = Maps.newHashMap();
        Map<String, Serializable> af = Maps.newHashMap();

        columMap.entrySet().forEach(entry -> {
            String key = entry.getKey();
            TableColum tableColum = entry.getValue();
            be.put(key, mapEntry.getKey()[tableColum.getInx()]);
            af.put(key, mapEntry.getValue()[tableColum.getInx()]);
        });

        item.before = be;
        item.after = af;
        return item;
    }

    /**
     * 获取对应sql
     * @return
     */
    public String generateSQL() {
        if (this.getEventType() == EventType.WRITE_ROWS) {
            return generateInsertSQL();
        } else if (this.getEventType() == EventType.UPDATE_ROWS) {
            // This is not an update event
            // Handle as per your requirement, maybe throw an exception.
            return generateUpdateSQL();
        } else if (this.getEventType() == EventType.DELETE_ROWS) {
            // This is not a delete event
            // Handle as per your requirement, maybe throw an exception.
            return generateDeleteSQL();
        } else {
            return null;
        }
    }

    private String generateInsertSQL() {


        StringBuilder sql = new StringBuilder("INSERT INTO ");
        sql.append(this.getSchema()).append(".").append(this.getTable()).append(" (");

        StringBuilder values = new StringBuilder(") VALUES (");

        for (Map.Entry<String, Serializable> entry : this.getAfter().entrySet()) {
            sql.append(entry.getKey()).append(", ");
            values.append(formatValue(entry.getValue())).append(", ");
        }

        // Remove trailing comma and space, then close with bracket
        sql.replace(sql.length() - 2, sql.length(), "");
        values.replace(values.length() - 2, values.length(), "");
        sql.append(values).append(")");

        return sql.toString();
    }

    private String generateUpdateSQL() {


        StringBuilder sql = new StringBuilder("UPDATE ");
        sql.append(this.getSchema()).append(".").append(this.getTable()).append(" SET ");

        for (Map.Entry<String, Serializable> entry : this.getAfter().entrySet()) {
            sql.append(entry.getKey()).append(" = ").append(formatValue(entry.getValue())).append(", ");
        }

        // Remove trailing comma and space
        sql.replace(sql.length() - 2, sql.length(), "");

        // Append the where clause
        sql.append(" WHERE ").append("id").append(" = ").append(formatValue(before.get("id")));

        return sql.toString();
    }

    private String generateDeleteSQL() {

        StringBuilder sql = new StringBuilder("DELETE FROM ");
        sql.append(this.getSchema()).append(".").append(this.getTable());

        // Append the where clause
        sql.append(" WHERE ").append("id").append(" = ").append(formatValue(before.get("id")));

        return sql.toString();
    }

    @SneakyThrows
    private String formatValue(Serializable value) {
        if (value instanceof String) {
            return "'" + value + "'";
        } else if (value instanceof byte[]) {
            return null;
            //return "'" + new String((byte[]) value, StandardCharsets.UTF_8) + "'";
        } else if (value instanceof Date) {
            return "'" + DateUtil.format(DateUtil.offsetHour(((Date) value), -8), DatePattern.NORM_DATETIME_FORMAT) + "'";
        } else {
            // Handle other data types as needed (dates, numbers, etc.)
            return String.valueOf(value);
        }
    }

}
